#include "simu.h"

/**
   initilize simulator data
   packet size and arrival interval are exponentially distribuited

   s: struct to initialize
   buffersize: size of queue buffer (in Bytes)
   pktint: packet arrival interval mean (miliseconds)
   pktsize: packet size mean (bytes)
   procspeed: packet processing speed (bytes/second)

   steps: number of simulation steps
   ignoredsteps: step number which data collection should start
 */
void simu_init(simu_t* s, uint64 buffersize, uint64 pktint, uint64 pktsize, uint64 procspeed,
               uint64 steps, uint64 ignoredsteps)
{
   // exponential dist mean is equal to 1/x where x is it's rate
   s->arrival_rate = 1.0 / (double)pktint;
   s->size_rate = 1.0 / (double)pktsize;

   s->procspeed = procspeed;
   s->buffer_size = buffersize;
   s->steps = steps;
   s->igsteps = ignoredsteps;
   s->clock = s->last_pkt_time = 0;
   s->queue = (pkt_t*)malloc(sizeof(pkt_t)*buffersize);
   s->next_free = 0;
   s->next_consume = 0;
   s->queue_elno = 0;
   s->queue_size = 0;

   s->stats.pkt_arr_num = 0;
   s->stats.pkt_proc_num = 0;
   s->stats.pkt_arr_bytes = 0;
   s->stats.pkt_proc_bytes = 0;
   s->stats.stat_clock = 0;
   s->stats.mean_delay = 0;
   s->stats.acm_queue_size = 0;
}

void simu_destroy(simu_t* s)
{
   if (s->queue)
      free(s->queue);
}

void create_pkt(simu_t *s, pkt_t* p)
{
   p->size = (uint64)rnd_exp(s->size_rate);
   s->last_pkt_time = p->arrival_time = (uint64)rnd_exp(s->arrival_rate) + s->last_pkt_time;   
   p->procinit_time = p->procfini_time = 0;
   p->dropped = 0;
}

/**
   queues or drops pkt
 */
void enqueue(simu_t* s, pkt_t* p)
{
   if (s->queue_elno == BUFFER_PKT_SIZE || s->queue_size + p->size > s->buffer_size)
   {
      // queue full - drop pkt
      p->dropped = 1;
   }
   else
   {
      memcpy(s->queue + s->next_free, p, sizeof(pkt_t));
      s->next_free = (s->next_free + 1) % s->buffer_size;
      s->queue_elno++;
      s->queue_size += p->size;
   }
}

// make sure queue is not empty before dequeuing
void dequeue(simu_t* s, pkt_t* p)
{
   if (s->queue_elno == 0)
   {
      fprintf(stderr, "Dequeue exception. Empty queue.\n");
      exit(1);
      return;
   }

   memcpy(p, s->queue + s->next_consume, sizeof(pkt_t));
   s->next_consume = (s->next_consume + 1) % s->buffer_size;
   s->queue_elno--;
   s->queue_size -= p->size;
}

void bookmark(simu_t* s, pkt_t* p, etype t)
{
   uint64 delay;
   double proc_n;

   if (s->curstep < s->igsteps)
      return;
   else if (s->stats.stat_clock == 0)
      s->stats.stat_clock = s->clock;

   if (t == ETYPE_PKT_PROCESSED)
   {
      delay = p->procfini_time - p->arrival_time;
      proc_n = (double)s->stats.pkt_proc_num;

      s->stats.mean_delay = ( s->stats.mean_delay ) * ( proc_n  / ( proc_n + 1 ) ) +
         ( delay ) / ( proc_n + 1 );
      s->stats.pkt_proc_num++; 
      s->stats.pkt_proc_bytes += p->size;
   }
   else if (t == ETYPE_PKT_ARRIVED)
   {
      s->stats.pkt_arr_num++;
      s->stats.pkt_arr_bytes += p->size;

      s->stats.acm_queue_size += s->queue_size;
   }
   else if (t == ETYPE_PKT_DROPPED)
   {
      // ignore for now
   }
}

/**
   start simulator with data
 */
void simu_start(simu_t* s)
{
   pkt_t p_new, p_proc;
   int i;
   uint64 next_event; /* time when next pkt will have its processing finished */

   rnd_init();

   // first iteration
   // PKT ARRIVED
   create_pkt(s, &p_proc);
   s->clock = p_proc.arrival_time;
   p_proc.procinit_time = s->clock;
   next_event = s->clock + PROC_TIME(p_proc.size, s->procspeed);
   bookmark(s, &p_proc, ETYPE_PKT_ARRIVED);

   // prepare next pkt
   create_pkt(s, &p_new);     

   for ( i = 0; i < s->steps; i++ )
   {
      // what happens first? does our p_proc pkt is processed
      // or a new pkt arrives?
      s->curstep = i;

      if (p_new.arrival_time < next_event)
      {
         // PKT ARRIVED

         s->clock = p_new.arrival_time;

         bookmark(s, &p_new, ETYPE_PKT_ARRIVED);

         // pkt arrives before processing finished       

         // add it to the queue
         enqueue(s, &p_new);

         if (p_new.dropped)
         {
            // PKT DROPPED
            bookmark(s, &p_new, ETYPE_PKT_DROPPED);
         }
         else
         {
            // PKT ENQUEUED
         }

         // prepare new pkt for next iteration
         create_pkt(s, &p_new);
      }
      else
      {
         // PKT PROCESSED

         s->clock = next_event;

         // a processed pkt has finished
         p_proc.procfini_time = s->clock;

         bookmark(s, &p_proc, ETYPE_PKT_PROCESSED);

         // get new pkt from queue, if any
         if (s->queue_elno)
         {
            // PKT DEQUEUED

            dequeue(s, &p_proc);
         }
         else
         {
            // PKT ARRIVED

            // just wait for new pkt
            p_proc = p_new;
            s->clock = p_proc.arrival_time;

            bookmark(s, &p_proc, ETYPE_PKT_ARRIVED);

            // prepare new pkt for next iterarion
            create_pkt(s, &p_new);
         }

         // set start process time
         p_proc.procinit_time = s->clock;
         // calculate next event
         next_event = s->clock + PROC_TIME(p_proc.size, s->procspeed);
      }
   }

}

uint64 get_args(int argn, char** argv)
{
   uint64 interval;

   if (argn != 2)
   {
      fprintf(stderr, "Using default 500ms pkt arrival mean interval\n");
      return 500;
   }
   
   interval = (uint64)atoll(argv[1]);

   printf("Using %llu pkt arrival mean interval\n", interval);

   return interval;
}

int main(int argn, char** argv)
{
   simu_t s;   
   double utilization, mean_queue_size;

   simu_init(&s, 1024*1024,  /* buffer size */
             get_args(argn, argv), /* pkt arrival interval mean */
             128, /* pkt mean size */
             512, /* (B/s processing speed) */
             1000000, /* steps */
             10000);  /* ignored steps*/

   simu_start(&s);

   utilization = ( s.stats.pkt_arr_bytes / ( (double)s.clock - s.stats.stat_clock ) * 1000.0 ) / ( (double)512  );

   mean_queue_size = s.stats.acm_queue_size / (double) s.stats.pkt_arr_num;

/*

   printf("Mean delay: %lf (ms)\n", s.stats.mean_delay);
   printf("Mean queue size: %lf (pkts)\n", mean_queue_size );
   printf("PktsProc: %lld\n", s.stats.pkt_proc_num);
   printf("PktsArrv: %lld\n", s.stats.pkt_arr_num);
   printf("PktsDrop: %lld (%lf%)\n", s.stats.pkt_arr_num - s.stats.pkt_proc_num,
          100 * ( s.stats.pkt_arr_num - s.stats.pkt_proc_num ) / (double) s.stats.pkt_arr_num);
   printf("Utilization: %lf\n", 
          utilization);
   printf("ArrBytes/s: %lf\n", 
          ( s.stats.pkt_arr_bytes / ( (double)s.clock - s.stats.stat_clock ) * 1000.0 ));
   printf("ArrPkts/s: %lf\n", 
          ( s.stats.pkt_arr_num / ( (double)s.clock - s.stats.stat_clock ) * 1000.0 ));

*/

   puts("Utilization\tMean delay(ms)\tMean queue size(pkts)");

   printf("%lf\t%lf\t%lf\n", utilization, s.stats.mean_delay, mean_queue_size);

   simu_destroy(&s);

   return 0;
}
